package com.yixing.tech.wms.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.convert.ConvertUtils;
import com.yixing.tech.common.entity.*;
import com.yixing.tech.common.service.AbstractWmsService;
import com.yixing.tech.wms.entity.InStockWork;
import com.yixing.tech.wms.service.ErpReportService;
import com.yixing.tech.wms.service.ProductInStockService;
import com.yixing.tech.wms.service.StockLocationService;
import com.yixing.tech.wms.service.WmsTaskService;
import com.yixing.tech.wms.stock.out.domain.Task;
import com.yixing.tech.wms.stock.out.domain.vo.TaskType;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.yixing.tech.common.CommonConstant.*;
import static com.yixing.tech.common.CommonConstant.InStatus.*;

/**
 * ProductInStockService
 *
 * @author yixing tech
 * @since 1.0.0
 */
@Service
public class ProductInStockServiceImpl extends AbstractWmsService implements ProductInStockService {
    @Value("${tenant-id: 6204cb763da841718ddd1cf8}")
    private String tenantId;
    @Autowired
    private StockLocationService stockLocationService;
    @Autowired
    private WmsTaskService wmsTaskService;
    @Autowired
    private ErpReportService erpReportService;

    /**
     * 根据容器编码执行成品入库
     *
     * @param containerCode
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean executionInByContainer(String containerCode) throws BusinessException {
        if (StringUtils.isBlank(containerCode)) {
            return false;
        }

        // 地点查询入库单
        List<Document> inOrders = simpleFind("entity_" + PRODUCTION_IN_STOCK, "sourceContainer", containerCode, "isStored", false);
        if (CollectionUtils.isEmpty(inOrders)) {
            throw new BusinessException(String.format("容器[%s]无成品入库单信息", containerCode));
        }

        List<String> inOrderCodes = inOrders.stream().map(x -> x.getString("inOrder")).collect(Collectors.toList());
        // 校验如果存在未完成的作业不能再次进行申请容器
        Document checkTask2 = mongoTemplate.findOne(
                Query.query(newCriteria().and("inOrder").in(inOrderCodes)
                        .and("inStatus.id").ne(new ObjectId(inStockOK.getCode()))),
                Document.class, "entity_" + IN_STOCK_TASK);
        if (checkTask2 != null) {
            throw new BusinessException(String.format("入库单[%s]存在未执行完成的上架作业，不能重复执行入库", inOrderCodes));
        }

        Document productionLocation = simpleFindOne("entity_" + STOCK_LOCATION, "containerCode", containerCode);
        if (productionLocation == null) {
            throw new BusinessException("场地没有这条地库信息");
        }
        String locationCode = productionLocation.getString("code");
        // 查询容器信息
        Document container = simpleFindOne("entity_" + CONTAINER, "code", containerCode);
        if (container == null) {
            throw new BusinessException(String.format("地库[%s]上没有容器或容器不存在", locationCode));
        }

        // 申请库位
        String materialCode = null;
        Document rule = null;
        Document stockLocation = null;
        String endWarehouseCode = null;
        String endAreaCode = null;
        String endLocCode = null;

        List<String> jobCodes = new LinkedList<>();
        // 校验如果存在未完成的作业不能再次进行申请容器
        for (Document inOrderObj : inOrders) {
            String inOrder = inOrderObj.getString("inOrder");
            List<Document> dtls = simpleFind("entity_" + PRODUCTION_IN_STOCK_DETAIL, "inOrder", inOrder);
            if (CollectionUtils.isEmpty(dtls)) {
                throw new BusinessException(String.format("入库单[%s]无入库明细", inOrder));
            }

            if (materialCode == null) {
                materialCode = dtls.get(0).getString("materialCode");
                rule = simpleFindOne("entity_" + STOCK_RULE, "materialCode", materialCode);
                if (rule == null) {
                    throw new BusinessException("物料规则不存在");
                }
                // 申请库位
                List<String> areas = rule.getList("areas", String.class);
                if (CollectionUtils.isEmpty(areas)) {
                    throw new BusinessException("物料没有对应的库区信息");
                }
                List<Document> locations = stockLocationService.queryEmptyLocation(areas, true);
                if (CollectionUtils.isEmpty(locations)) {
                    throw new BusinessException("仓库中没有空闲的空位！");
                }

                stockLocation = locations.get(0);

                // 上锁
                Update update = new Update();
                update.set("lock", true);
                if (!versionUpdateById(stockLocation, update, "entity_" + STOCK_LOCATION)) {
                    throw new BusinessException("申请库位资源上锁失败！");
                }
                assertBiz(() -> locations.size() <= 4, "A库库位不足以分配");
                // 目标地点
                endWarehouseCode = stockLocation.get("areaObj", Document.class).getString("warehouse");
                endAreaCode = stockLocation.get("areaObj", Document.class).getString("code");
                endLocCode = stockLocation.getString("code");
            }

            // 创建上架作业：入库状态改为待入库
            for (Document dtl : dtls) {
                // 校验物料信息是否存在
                Document stock = simpleFindOne("entity_" + STOCK_NAME, "productionInStockDtlKey", dtl.get("dtlKey"));
                if (stock == null) {
                    throw new BusinessException("场地没有这条物料信息");
                }
                double dtlRealNum = stock.getDouble("realNum");
                String taskCode = new ObjectId().toString();
                jobCodes.add(taskCode);
                InStockWork inStockWork = new InStockWork();
                inStockWork.setCode(taskCode);
                inStockWork.setInOrder(inOrder);
                inStockWork.setInOrderDtlId(dtl.get("id").toString());
                inStockWork.setDtlSourceTable(PRODUCTION_IN_STOCK_DETAIL);
                inStockWork.setInSource(dtl.getString("inSource"));
                inStockWork.setInDetailSource(dtl.getString("inDetailSource"));
                inStockWork.setMaterialCode(materialCode);
                inStockWork.setMaterialName(dtl.getString("materialName"));
                inStockWork.setColorName(dtl.getString("colorName"));
                inStockWork.setLanguageName(dtl.getString("languageName"));
                inStockWork.setBatchNo(dtl.getString("batchNo"));
                inStockWork.setContainerCode(containerCode);
                inStockWork.setContainerType(newDocument("id", new ObjectId(container.getEmbedded(Arrays.asList("type", "id"), String.class))));
                inStockWork.setInStatus(newDocument("id", new ObjectId(inStocking.getCode())));
                inStockWork.setPlanNum(dtl.getDouble("planInNum"));
                inStockWork.setMatchNum(dtlRealNum);
                inStockWork.setRealNum(dtlRealNum);
                inStockWork.setPartitionSide("1");
                inStockWork.setOutLocationCode("");
                inStockWork.setErpWarehouse(dtl.getString("erpWarehouse"));
                inStockWork.setAreaCode(endAreaCode);
                inStockWork.setLocationCode(endLocCode);
                inStockWork.setInStockType(newDocument("id", new ObjectId(InStockType.productionInStock.getCode())));
                inStockWork.setAbc(A);
                inStockWork.setTenantId(tenantId);
                mongoTemplate.insert(inStockWork, "entity_" + IN_STOCK_TASK);
            }
        }

        if (endLocCode != null) {
            // wcs
            // 创建机器人上架任务
            Task task = Task.builder()
                    .taskCode(UUID.randomUUID().toString().replaceAll("-", ""))
                    .taskType(TaskType.STOCK_IN.getId())
                    .startLoc(locationCode)
                    .endArea(endAreaCode)
                    .endLoc(endLocCode)
                    .podCode(containerCode)
                    .business(IN_STOCK_BIZ)
                    .operationNo(jobCodes)
                    .build();
            wmsTaskService.saveTaskInfo(task);
        }

        return true;
    }

    @Override
    public boolean reportErp(String dtlId) throws BusinessException {
        if (StringUtils.isBlank(dtlId)) {
            throw new BusinessException("id不能为空");
        }
        Document dtlData = simpleFindOne("entity_" + PRODUCTION_IN_STOCK_DETAIL, "_id", dtlId);
        if (dtlData == null) {
            throw new BusinessException(String.format("成品入库明细[%s]不存在", dtlId));
        }

        String erpBillId = dtlData.getString("erpBillId");
        if (StringUtils.isNoneBlank(erpBillId)) {
            throw new BusinessException("已经完成Erp上报，不能重复上报");
        }
        // 是否完成的入库，查询这个单据关联的所有的上架作业
        Document checkTask2 = mongoTemplate.findOne(
                Query.query(newCriteria().and("inOrderDtlId").is(dtlId)
                        .and("inStatus.id").ne(new ObjectId(inStockOK.getCode()))),
                Document.class, "entity_" + IN_STOCK_TASK);
        if (checkTask2 != null) {
            throw new BusinessException(String.format("成品入库明细[%s]存在未执行完成的上架作业，不能上报", dtlId));
        }

        // 查询单据头
        Document head = simpleFindOne("entity_" + PRODUCTION_IN_STOCK, "inOrder", dtlData.getString("inOrder"));
        if (head == null) {
            throw new BusinessException(String.format("成品入库单头[%s]不存在", dtlData.getString("inOrder")));
        }

        String soapMethod;
        String param;
        ErpReturn erpReturn;
        Document qmsProductionInStock = dtlData.get("qmsProductionInStock", Document.class);
        ErpProdInStock prodInStock = new ErpProdInStock();

        prodInStock.setRuleCode(qmsProductionInStock.getString("ruleNumber"));
        prodInStock.setSourceBillId(qmsProductionInStock.getString("sourceBillId"));
        prodInStock.setBizType(head.getString("erpBizType"));
        prodInStock.setTransactionType(head.getString("erpTransactionType"));
        prodInStock.setCostCenterOrgUnit(head.getString("erpCostCenterOrgUnit"));
        prodInStock.setStorageOrgUnit(head.getString("erpStorageOrgUnit"));
        prodInStock.setAdminOrgUnit(head.getString("erpAdminOrgUnit"));

        ErpProdInStockEntry entry = new ErpProdInStockEntry();
        entry.setSourceEntryId(qmsProductionInStock.getString("sourceBillEntryId"));
        entry.setMaterial(dtlData.getString("materialCode"));
        entry.setQty(dtlData.getDouble("realInNum") + "");
        entry.setWarehouse(dtlData.getString("erpWareHouse"));
        entry.setCostCenterOrgUnit(qmsProductionInStock.getString("costCenterOrgUnit"));
        entry.setCostObject(qmsProductionInStock.getString("costObject"));
        prodInStock.setEntrys(Arrays.asList(entry));

        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
        try {
            param = mapper.writeValueAsString(prodInStock);
        } catch (JsonProcessingException e) {
            throw new BusinessException(e);
        }
        soapMethod = "CreateProdInStock";

        // 上报
        String result = erpReportService.erpReport(soapMethod, param, "CreateProdInStockReturn");
        ObjectMapper mapper2 = new ObjectMapper();
        try {
            Map map = mapper2.readValue(new ByteArrayInputStream(result.getBytes()), Map.class);
            erpReturn = mapper2.convertValue(map, ErpReturn.class);
        } catch (IOException e) {
            throw new BusinessException(e);
        }

        // 更新单据
        Update update = new Update();
        int status = erpReturn.getStatus();
        if (status == 0) {
            update.set("erpReport", true);
        } else {
            update.set("erpReport", false);
        }
        update.set("erpAudit", erpReturn.getStatus() == 0);
        update.set("erpMsg", erpReturn.getMsg());
        update.set("erpReportTime", new Date());
        update.set("erpBizDate", ConvertUtils.convertIfPossible(erpReturn.getBillDate(), Date.class));
        update.set("erpBillNumber", erpReturn.getBillNumber());
        update.set("erpBillId", erpReturn.getBillId());
        updateById(dtlData, update, "entity_" + PRODUCTION_IN_STOCK_DETAIL);

        return false;
    }

}
